// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019. All rights reserved.
 * Description: support nand flash driver
 * Author: luojie <luojie6@huawei.com>
 * Create: 2019-11-06
 */

#include "hifmc.h"
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/mutex.h>
#include <linux/math64.h>
#include <linux/slab.h>
#include <linux/dma-mapping.h>
#include <linux/mtd/partitions.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include "mtdcore.h"
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include "hisfc300.h"


#define HIFMC_SUPPORT_MAX_DUMMY  1
#define HIFMC_SUPPORT_WRITE (SPI_IF_WRITE_STD)
#define HIFMC_SUPPORT_READ (SPI_IF_READ_STD)

#define USER_PROTECT_ON 1    /* The user opens the flash protection */
#define USER_PROTECT_OFF 0    /* The user disables the flash protection */
#define SFC_PROTECT_ON 1    /* flash protection has been protected logo */
#define SFC_PROTECT_OFF 0    /* flash protection unprotected logo */

/* Whether the user set the FLASH cancel protection flag */
static int fmc_protect_status = USER_PROTECT_ON;
/* Flash itself is in the protection status of identification */
static int fmc_protect_flag = SFC_PROTECT_OFF;
/* flash is currently in write state */
static int fmc_erase_write_flag = SPI_IF_ERASE_WRITE_DIS;
/* spi lock work */
static struct delayed_work spi_lock_work;
static unsigned char g_spi_ids[8] = {0};  // macro MTD_CHIP_ID_LEN = 8

#ifdef CONFIG_MTD_PARTITIONS
#define mtd_has_partitions() 1
#else
#define mtd_has_partitions() 0
#endif /* CONFIG_MTD_PARTITIONS */
#ifdef CONFIG_MTD_CMDLINE_PARTS
static const char * const part_probes[] = {"cmdlinepart", NULL, };
#endif

#ifndef __tagtable
#define __tagtable(tag, fn) \
static const struct tagtable __tagtable_##fn __used __section(".taglist.init") = { tag, fn }
#endif

struct hisfc_ew_count {
	unsigned int erasetimes;
	unsigned int writetimes;
};

/* declare internal interface */
static int hifmc_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len);
static int hifmc_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len);
static void hifmc_lock_timer(void);
static void hifmc_lock_timer_handler(struct work_struct *work);

static void hifmc_of_node_parse(struct platform_device *dev, const char *of_name)
{
	struct device_node *node = NULL;
	u32 fmc_reg_base[2] = {0};
	u32 fmc_mem_base[2] = {0};

	node = of_find_compatible_node(NULL, NULL, of_name);
	if (node == NULL)
		return;

	if (of_property_read_u32_array(node, "reg_base", fmc_reg_base, 2))
		goto ERROR;

	if (of_property_read_u32_array(node, "mem_base", fmc_mem_base, 2))
		goto ERROR;

	dev->resource[0].start = fmc_reg_base[0];
	dev->resource[0].end = fmc_reg_base[0] + fmc_reg_base[1];
	dev->resource[1].start = fmc_mem_base[0];
	dev->resource[1].end = fmc_mem_base[0] + fmc_mem_base[1];
	return;
ERROR:
	pr_info("Can not find fmc-nor reg_base or mem_base in dtb, fmc will use default base addr");
}

static char *int_to_size(unsigned int size)
{
	int ix;
	static char buffer[FMC_FMT_SIZE];
	static const char * const fmt[] = {"%u", "%uK", "%uM", "%uG", "%uT", "%uT"};

	/* 5 is the number of FMT modes */
	for (ix = 0; (ix < 5) && !(FMC_SIZE_DATA(size)) && size; ix++)
		size = FMC_SIZE_CMD(size);

	snprintf(buffer, FMC_FMT_SIZE, fmt[ix], size);
	return buffer;
}

static unsigned int hifmc_reg_read(const char *reg_base, unsigned int reg)
{
	return (unsigned int)readl(reg_base + reg);
}

static void hifmc_reg_write(char *reg_base, unsigned int reg,
	unsigned int value)
{
	writel(value, reg_base + reg);
}

int hifmc_status_check(struct hifmc_host *host, unsigned int reg)
{
	unsigned long regval;
	unsigned long deadline;

	deadline = jiffies + HIMFC300_MAX_READY_WAIT_JIFFIES;

	do {
		regval = hifmc_reg_read(host->regbase, reg);
		if (!(regval & FMC_FLAG))
			return 0;

		cond_resched();
	} while (!time_after_eq(jiffies, deadline));

	pr_info("FMC: controller operation time out, CS%d!\n", host->start_cs);
	return 1;
}

int hifmc_ecctype_set(struct hifmc_host *host, unsigned int ecc_value)
{
	unsigned int reg_value;

	reg_value = hifmc_reg_read(host->regbase, FMC_CFG);
	reg_value &= ~FMC_ECC_CAL;
	reg_value |= FMC_ECC_BUF(ecc_value);

	hifmc_reg_write(host->regbase, FMC_CFG, reg_value);

	return 0;
}

int hifmc_read_chipid_cs(struct hifmc_host *host, unsigned int cs,
	unsigned char *buffer, int numread)
{
	unsigned int i;
	unsigned int id_num = numread;

	if (numread > NOR_FLASH_ID_MAX)
		id_num = NOR_FLASH_ID_MAX;

	/* clear buffer */
	for (i = 0; i < id_num; i++)
		hifmc_reg_write(host->iobase, i, 0);

	hifmc_ecctype_set(host, 0);
	hifmc_reg_write(host->regbase, FMC_CMD, SPI_CMD_RDID);
	hifmc_reg_write(host->regbase, FMC_DATA_NUM, FMC_BIT_FIVE);
	hifmc_reg_write(host->regbase, FMC_OP_CFG,
		(cs & FMC_FLAG) << FMC_VALUE_MOVE);
	hifmc_reg_write(host->regbase, FMC_OP, FMC_ADDR);

	if (hifmc_status_check(host, FMC_OP))
		return -1;

	for (i = 0; i < id_num; i++)
		buffer[i] = hifmc_reg_read(host->iobase, i);

	return 0;
}

int hifmc_read_chipid(struct mtd_info *mtd, unsigned char *buffer, int numread)
{
	struct hifmc_host *host = FMC_MTD_TO_HOST(mtd);
	unsigned int cs;

	cs = host->start_cs;
	mutex_lock(&host->lock);
	hifmc_read_chipid_cs(host, cs, buffer, numread);
	mutex_unlock(&host->lock);

	return 0;
}

static int hifmc_dma_read_page(struct hifmc_host *host, unsigned int offset,
	unsigned int cs, void *buf, unsigned int len)
{
	struct hifmc_spi *spi = NULL;

	if (len == 0)
		return 0;

	DBG_MSG("FMC: dma read cs%d, offset 0x%x, len 0x%x, data 0x%x\n",
			cs, offset, len, (unsigned int)buf);

	spi = &(host->spi[cs]);
	hifmc_reg_write(host->regbase, FMC_ADDRL, offset);
	hifmc_reg_write(host->regbase, FMC_DMA_SADDR_D0,
					(unsigned int)(host->dma_buffer));
	hifmc_reg_write(host->regbase, FMC_DMA_LEN, len);
	hifmc_reg_write(host->regbase, FMC_OP_CFG,
				(cs & FMC_FLAG) << FMC_VALUE_MOVE |
				(spi->read[0].iftype & FMC_BIT_SEVEN) << FMC_CFG_MOVE |
				(spi->read[0].dummy & FMC_DUMMY_MOVE));
	hifmc_reg_write(host->regbase, FMC_OP_CTRL, FMC_DMA_EN |
				(spi->read[0].cmd & FMC_REG_MAX) << FMC_CMD_READ);

	if (hifmc_status_check(host, FMC_OP_CTRL)) {
		pr_err("FMC: device read fail\n");
		return -1;
	}

	memcpy(buf, host->buffer, len);

	return 0;
}

static int hifmc_dma_read(struct hifmc_host *host, unsigned int offset,
	unsigned int cs, void *buf, unsigned int len)
{
	unsigned int page_size;
	unsigned int read_size = 0;
	unsigned int read_cnt = 0;
	int ret = 0;

	page_size = host->dma_buffer_size;

	while (len > 0) {
		if (len > page_size)
			read_size = page_size;
		else
			read_size = len;

		ret = hifmc_dma_read_page(host, (offset + read_cnt), cs,
			(buf + read_cnt), read_size);
		if (ret != 0)
			return -1;

		read_cnt += read_size;
		len -= read_size;
	};

	return 0;
}

static int hifmc_read(struct mtd_info *mtd, loff_t from, size_t len,
	size_t *retlen, u_char *buf)
{
	struct hifmc_host *host = NULL;
	unsigned int start_cs_size = 0;
	unsigned int unstart_cs_size = 0;
	unsigned int startcs_len = 0;
	unsigned int unstartcs_len = 0;
	unsigned int unstartcs = 0;
	unsigned int startcs = 0;
	int result = -EIO;

	if ((len == 0) || ((from + len) > mtd->size)) {
		pr_info("len=%zu, from=%lld, mtd->size=%llu\r\n",
			len, from, mtd->size);
		return -EINVAL;
	}

	DBG_MSG("FMC: fmc_norflash_read  from 0x%x, len 0x%x, memory buf 0x%x\n",
			from, len, (unsigned int)buf);

	host = FMC_MTD_TO_HOST(mtd);
	mutex_lock(&host->lock);
	startcs = host->start_cs;
	unstartcs = (host->start_cs == 0) ? 1 : 0;
	start_cs_size = host->spi[startcs].chipsize;
	unstart_cs_size = host->spi[unstartcs].chipsize;

	if (from < start_cs_size) {
		startcs_len = (start_cs_size - from) > len ?
			len : (start_cs_size - from);
		unstartcs_len = len - startcs_len;

		if (hifmc_dma_read(host, from, host->start_cs, buf, startcs_len) ||
			hifmc_dma_read(host, 0, unstartcs,
					buf + startcs_len, unstartcs_len)) {
			goto fail;
		}
	} else {
		if (hifmc_dma_read(host, start_cs_size, unstartcs, buf, len))
			goto fail;
	}

	*retlen = len;
	result = 0;

fail:
	mutex_unlock(&host->lock);
	return result;
}

static int hifmc_dma_write_page(struct hifmc_host *host, unsigned int offset,
	unsigned int cs, const void *buf, unsigned int len)
{
	struct hifmc_spi *spi = NULL;

	if (len == 0)
		return 0;

	DBG_MSG("FMC: dma write cs%d, offset 0x%x, len 0x%x, data 0x%x\n",
			cs, offset, len, (unsigned int)buf);

	spi = &(host->spi[cs]);
	memcpy(host->buffer, buf, len);
	hifmc_reg_write(host->regbase, FMC_ADDRL, offset);
	hifmc_reg_write(host->regbase, FMC_DMA_SADDR_D0,
			(unsigned int)(host->dma_buffer));
	hifmc_reg_write(host->regbase, FMC_DMA_LEN, len);
	hifmc_reg_write(host->regbase, FMC_OP_CFG,
			(cs & FMC_FLAG) << FMC_VALUE_MOVE |
			(FMC_CFG & FMC_BIT_SEVEN) << FMC_CFG_MOVE |
			(spi->write[0].dummy & FMC_DUMMY_MOVE));
	hifmc_reg_write(host->regbase, FMC_OP_CTRL, FMC_DMA_EN |
		FMC_CONFIG_ADDR_MODE |
		(spi->write[0].cmd & FMC_REG_MAX) << NUMREAD_NUM);

	DBG_MSG("spi->write[0].iftype=0x%x, spi->write[0].dummy=0x%x, spi->write[0].cmd=0x%x\r\n",
			spi->write[0].iftype, spi->write[0].dummy, spi->write[0].cmd);

	if (hifmc_status_check(host, FMC_OP_CTRL)) {
		pr_info("FMC: device write fail\n");
		return -1;
	}

	return 0;
}

static int hifmc_dma_write(struct hifmc_host *host, unsigned int offset,
	unsigned int cs, const void *buf, unsigned int len)
{
	unsigned int page_size;
	unsigned int write_size = 0;
	unsigned int write_cnt = 0;
	int ret = 0;

	page_size = host->dma_buffer_size;

	while (len > 0) {
		if (len > page_size)
			write_size = page_size;
		else
			write_size = len;

		DBG_MSG("FMC: write offset 0x%x, buffer offset 0x%x, write_size 0x%x\r\n",
			(offset + write_cnt), (buf + write_cnt), write_size);
		ret = hifmc_dma_write_page(host, (offset + write_cnt),
				cs, (buf + write_cnt), write_size);
		if (ret != 0)
			return -1;

		write_cnt += write_size;
		len -= write_size;
	};

	return 0;
}

static int hifmc_write(struct mtd_info *mtd, loff_t to, size_t len,
	size_t *retlen, const u_char *buf)
{
	struct hifmc_host *host = NULL;
	unsigned int start_cs_size;
	unsigned int unstart_cs_size;
	unsigned int startcs_len;
	unsigned int unstartcs_len;
	unsigned int unstartcs;
	int result = -EIO;

	if (!len || ((to + len) > mtd->size))
		return -EINVAL;

	host = FMC_MTD_TO_HOST(mtd);
	DBG_MSG("FMC: write  offset 0x%x, len 0x%x, memory buf 0x%x\n",
			to, len, (unsigned int)buf);

	mutex_lock(&host->lock);
	*retlen = 0;
	unstartcs = (host->start_cs == 0) ? 1 : 0;
	start_cs_size = host->spi[host->start_cs].chipsize;
	unstart_cs_size = host->spi[unstartcs].chipsize;

	fmc_erase_write_flag = SPI_IF_ERASE_WRITE_EN;
	hifmc_unlock(mtd, 0, 0);

	if (to < start_cs_size) {
		startcs_len = (start_cs_size - to) > len ?
					len : (start_cs_size - to);
		unstartcs_len = len - startcs_len;
		if (hifmc_dma_write(host, to, host->start_cs, buf, startcs_len) ||
			hifmc_dma_write(host, 0, unstartcs,
				buf + startcs_len, unstartcs_len)) {
			goto fail;
		}
	} else {
		if (hifmc_dma_write(host, to - start_cs_size, unstartcs, buf, len))
			goto fail;
	}

	*retlen = len;
	result = 0;

fail:
	fmc_erase_write_flag = SPI_IF_ERASE_WRITE_DIS;
	mutex_unlock(&host->lock);
	hifmc_lock_timer();
	return result;
}

static int hifmc_block_erase(struct hifmc_host *host, unsigned int offset,
	unsigned int cs)
{
	struct hifmc_spi *spi = NULL;

	spi = &(host->spi[cs]);

	hifmc_reg_write(host->regbase, FMC_CMD, FMC_CMD_MOVE);
	hifmc_reg_write(host->regbase, FMC_OP, FMC_OP_EN1);
	if (hifmc_status_check(host, FMC_OP))
		return -1;

	hifmc_reg_write(host->regbase, FMC_CMD, FMC_CMD_DATA);
	hifmc_reg_write(host->regbase, FMC_ADDRL, offset);
	hifmc_reg_write(host->regbase, FMC_OP_CFG,
				(cs & FMC_FLAG) << FMC_VALUE_MOVE |
				(FMC_ADEDRESS_BITE & FMC_BIT_SEVEN) << FMC_BIT_FOUR);
	hifmc_reg_write(host->regbase, FMC_OP, FMC_OP_DATA);
	if (hifmc_status_check(host, FMC_OP))
		return -1;

	hifmc_reg_write(host->regbase, FMC_CMD, FMC_BIT_FIVE);
	hifmc_reg_write(host->regbase, FMC_DATA_NUM, FMC_FLAG);
	hifmc_reg_write(host->regbase, FMC_OP, FMC_BIT_THREE);
	if (hifmc_status_check(host, FMC_OP))
		return -1;

	while (hifmc_reg_read(host->iobase, 0) & FMC_FLAG) {
		hifmc_reg_write(host->regbase, FMC_OP, FMC_BIT_THREE);
		if (hifmc_status_check(host, FMC_OP))
			return -1;
	}

	if (hifmc_reg_read(host->iobase, 0) & GLOBAL_CFG) {
		pr_err("MFC: device erase fail\n");
		return -1;
	}

	return 0;
}

static int hifmc_erase(struct mtd_info *mtd, struct erase_info *instr)
{
	struct hifmc_host *host = NULL;
	unsigned int unstart_cs;
	unsigned int start_cs_size;
	unsigned int unstart_cs_size;
	unsigned int start_cs_erasesize;
	unsigned int unstart_cs_erasesize;
	unsigned int offset;
	unsigned int len;
	unsigned int eraseaddr;
	unsigned int startcs_len = 0;
	unsigned int unstartcs_len = 0;
	int result = -EIO;

	offset = instr->addr;
	len = instr->len;

	if ((!len) || ((offset + len) > mtd->size))
		return -EINVAL;

	host = FMC_MTD_TO_HOST(mtd);
	mutex_lock(&host->lock);
	offset = instr->addr;
	len = instr->len;

	unstart_cs = (host->start_cs == 0) ? 1 : 0;
	start_cs_size = host->spi[host->start_cs].chipsize;
	unstart_cs_size = host->spi[unstart_cs].chipsize;
	start_cs_erasesize = host->spi[host->start_cs].erasesize;
	unstart_cs_erasesize = host->spi[unstart_cs].erasesize;

	fmc_erase_write_flag = SPI_IF_ERASE_WRITE_EN;
	hifmc_unlock(mtd, 0, 0);
	if (offset < start_cs_size) {
		startcs_len = (start_cs_size - offset) > len ?
			len : (start_cs_size - offset);
		unstartcs_len = len - startcs_len;
		if ((startcs_len % start_cs_erasesize) ||
			(offset % start_cs_erasesize)) {
			pr_err("MFC: erase address don't aligned as blocksize\n");
			goto fail;
		}

		eraseaddr = offset;
		while (startcs_len) {
			if (hifmc_block_erase(host, eraseaddr, host->start_cs)) {
				pr_info("MFC: erase cs%d faile, address 0x%x\n",
					host->start_cs, eraseaddr);
				goto fail;
			}

			eraseaddr += start_cs_erasesize;
			startcs_len -= start_cs_erasesize;
		}

		if (unstartcs_len == 0)
			goto complete;

		eraseaddr = 0;

		if ((!unstart_cs_erasesize) || (unstartcs_len % unstart_cs_erasesize)) {
			pr_err("MFC: no unstartcs detect or unstartcs erase ");
			pr_err("address don't aligned as blocksize\n");
			goto fail;
		}

		while (unstartcs_len) {
			if (hifmc_block_erase(host, eraseaddr, unstart_cs)) {
				pr_info("MFC: erase cs%d faile, address 0x%x\n",
					unstart_cs, eraseaddr);
				goto fail;
			}

			eraseaddr += unstart_cs_erasesize;
			unstartcs_len -= unstart_cs_erasesize;
		}
	} else {
		eraseaddr = offset - start_cs_size;
		if ((!unstart_cs_erasesize) || (eraseaddr % unstart_cs_erasesize) ||
			(len % unstart_cs_erasesize)) {
			pr_err("MFC: no unstartcs detect or unstartcs erase ");
			pr_err("address or length don't aligned as blocksize\n");
			goto fail;
		}

		while (len) {
			if (hifmc_block_erase(host, eraseaddr, unstart_cs)) {
				pr_info("MFC: erase cs%d faile, address 0x%x\n",
					unstart_cs, eraseaddr);
				goto fail;
			}

			eraseaddr += unstart_cs_erasesize;
			instr->len -= unstart_cs_erasesize;
		}
	}

complete:
	result = 0;

fail:
	fmc_erase_write_flag = SPI_IF_ERASE_WRITE_DIS;
	mutex_unlock(&host->lock);
	hifmc_lock_timer();

	return result;
}

static int hifmc_read_sr(struct hifmc_host *host, unsigned int cs)
{
	unsigned int regval = 0;

	/* read data */
	hifmc_reg_write(host->regbase, FMC_CMD, FMC_BIT_FIVE);
	hifmc_reg_write(host->regbase, FMC_DATA_NUM, FMC_FLAG);
	hifmc_reg_write(host->regbase, FMC_OP_CFG,
		(host->start_cs & FMC_FLAG) << FMC_VALUE_MOVE);
	hifmc_reg_write(host->regbase, FMC_OP, FMC_ADDR);
	if (hifmc_status_check(host, FMC_OP))
		return -1;

	regval = hifmc_reg_read(host->iobase, 0);

	return regval & (FMC_CMD_NAND_RESET);
}

static int hifmc_write_sr(struct hifmc_host *host, unsigned int cs,
	unsigned int value)
{
	/* write enable */
	hifmc_reg_write(host->regbase, FMC_CMD, FMC_BIT_SIX);
	hifmc_reg_write(host->regbase, FMC_OP, FMC_OP_EN1);
	if (hifmc_status_check(host, FMC_OP))
		return -1;

	/* Write data */
	hifmc_reg_write(host->iobase, 0, value);

	/* direct write instructions */
	hifmc_reg_write(host->regbase, FMC_CMD, FMC_FLAG);
	hifmc_reg_write(host->regbase, FMC_DATA_NUM, FMC_FLAG);
	hifmc_reg_write(host->regbase, FMC_OP_CFG,
		(host->start_cs & FMC_FLAG) << FMC_VALUE_MOVE);
	hifmc_reg_write(host->regbase, FMC_OP, FMC_OP_EN2);

	/* Check the write operation  status */
	if (hifmc_status_check(host, FMC_OP))
		return -1;

	return 0;
}

static int hifmc_hardware_wp_enable(struct hifmc_host *host)
{
	unsigned int regval;

	regval = hifmc_reg_read(host->regbase, GLOBAL_CFG);
	regval |= FMC_WP_EN;
	hifmc_reg_write(host->regbase, GLOBAL_CFG, regval);
	mdelay(1); /* delay 1ms */

	return 0;
}

static int hifmc_hardware_wp_disable(struct hifmc_host *host)
{
	unsigned int regval;

	regval = hifmc_reg_read(host->regbase, GLOBAL_CFG);
	regval &= ~FMC_WP_EN;
	hifmc_reg_write(host->regbase, GLOBAL_CFG, regval);
	mdelay(1); /* delay 1ms */

	return 0;
}

void hifmc_lock_timer_handler(struct work_struct *work)
{
	struct mtd_info *mtd = NULL;
	struct hifmc_host *host = NULL;

	mtd = get_mtd_device_nm("hi_fmc");
	if (mtd == NULL) {
		pr_err("not find\n");
		return;
	}

	host = FMC_MTD_TO_HOST(mtd);

	if (fmc_erase_write_flag == SPI_IF_ERASE_WRITE_EN) {
		/* If you are writing, restart the work queue */
		hifmc_lock_timer();
	} else {
		mutex_lock(&host->lock);
		hifmc_lock(mtd, 0, 0);
		mutex_unlock(&host->lock);
	}
}

static void hifmc_lock_timer(void)
{
	if (fmc_protect_status == USER_PROTECT_OFF)
		return;

	/*The work queue is opened when the flash is unprotected*/
	if (fmc_protect_flag == SFC_PROTECT_OFF) {
		if (delayed_work_pending(&spi_lock_work)) {
			cancel_delayed_work(&spi_lock_work);
			flush_scheduled_work();
		}

		/* delay 1S */
		schedule_delayed_work(&spi_lock_work, msecs_to_jiffies(DELAY_TIME));
	}
}

static int hifmc_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
{
	struct hifmc_host *host = FMC_MTD_TO_HOST(mtd);
	struct hifmc_spi *spi = NULL;
	unsigned char bp_value;
	unsigned int reg_value;
	unsigned int start_cs;

	start_cs = host->start_cs;
	spi = &(host->spi[start_cs]);
	pr_info("Lock Spi Flash(cs%d):BP~0x%x!\n", start_cs, spi->BP_bitmask);

	/* If the user set to cancel the flash protection,
	* unless the time to restart the probe with flash protection,
	* or protection has been canceled
	*/
	if (fmc_protect_status == USER_PROTECT_OFF) {
		pr_info("User set spi flash protect off!\n");
		return 0;
	}

	fmc_protect_flag = SFC_PROTECT_ON;
	/* read status reg */
	reg_value = hifmc_read_sr(host, start_cs);
	bp_value = SPI_CMD_SR_SRWD | (spi->BP_bitmask << FMC_BP_MOVE);

	/* judge whether it has been protected */
	if (bp_value != (reg_value & bp_value)) {
		hifmc_hardware_wp_disable(host);
		/* change reg value to enable protect */
		reg_value |= bp_value;
		/* write back to status reg */
		hifmc_write_sr(host, start_cs, reg_value);
		hifmc_hardware_wp_enable(host);
	}

	pr_info("Lock Spi Flash Success!\n");
	return 0;
}

static int hifmc_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
{
	struct hifmc_host *host = FMC_MTD_TO_HOST(mtd);
	struct hifmc_spi *spi = NULL;
	unsigned int reg_value;
	unsigned char bp_value;
	unsigned int start_cs;

	/* If the user set to cancel the flash protection,
	* unless the time to restart the probe with flash protection,
	* or protection has been canceled
	*/
	if (fmc_protect_status == USER_PROTECT_OFF)
		return -1;

	/* flash is currently in an unprotected state */
	if (fmc_protect_flag == SFC_PROTECT_OFF)
		return -1;

	start_cs = host->start_cs;
	spi = &(host->spi[start_cs]);
	pr_info("Unlock Spi Flash(cs%d):BP~0x%x!\n", start_cs, spi->BP_bitmask);

	fmc_protect_flag = SFC_PROTECT_OFF;
	reg_value = hifmc_read_sr(host, start_cs);
	bp_value = SPI_CMD_SR_SRWD | (spi->BP_bitmask << FMC_BP_MOVE);

	/* Determine whether the protection has been canceled */
	if ((reg_value & bp_value) != 0) {
		hifmc_hardware_wp_disable(host);

		/* change reg value to disable protect */
		reg_value &= ~bp_value;

		/* write back to status reg */
		hifmc_write_sr(host, start_cs, reg_value);

		hifmc_hardware_wp_enable(host);
	}

	return 0;
}

unsigned int hifmc_protect_set(unsigned int wp)
{
	struct mtd_info *mtd = NULL;
	struct hifmc_host *host = NULL;
	struct hifmc_spi *spi = NULL;

	mtd = get_mtd_device_nm("hi_fmc");
	if (mtd == 0) {
		pr_err("FMC:fmc not find\n");
		return -EINVAL;
	}

	host = FMC_MTD_TO_HOST(mtd);
	spi = &(host->spi[host->start_cs]);

	mutex_lock(&host->lock);
	if (wp == USER_PROTECT_ON) {
		fmc_protect_status = USER_PROTECT_ON;
		hifmc_lock(mtd, 0, 0);
	} else {
		hifmc_unlock(mtd, 0, 0);
		fmc_protect_status = USER_PROTECT_OFF;
	}
	mutex_unlock(&host->lock);

	return 0;
}

unsigned int hifmc_protect_get(unsigned int *wp)
{
	struct mtd_info *mtd = NULL;
	struct hifmc_host *host = NULL;
	struct hifmc_spi *spi = NULL;
	unsigned int reg_value;
	unsigned int wp_sr_mask;
	int cs;

	mtd = get_mtd_device_nm("hi_fmc");
	if (mtd == 0) {
		pr_err("FMC: fmc not find\n");
		return -EINVAL;
	}

	host = FMC_MTD_TO_HOST(mtd);
	spi = &(host->spi[host->start_cs]);
	wp_sr_mask = (SPI_CMD_SR_SRWD | (spi->BP_bitmask << FMC_BP_MOVE));
	mutex_lock(&host->lock);

	/* check status reg */
	cs = spi->chipselect;
	reg_value = hifmc_read_sr(host, (unsigned int)cs);
	if (wp_sr_mask != (reg_value & wp_sr_mask)) {
		*wp = SFC_PROTECT_OFF;
		mutex_unlock(&host->lock);
		return 0;
	}

	*wp = SFC_PROTECT_ON;
	mutex_unlock(&host->lock);

	return 0;
}

static unsigned int hifmc_map_chipsize(unsigned long long chipsize)
{
	unsigned int shift = 0;

	/* 19: 512K = 2^19 is the address code digits; 3: Bytes */
	chipsize >>= (19 - 3);

	while (chipsize) {
		chipsize >>= 1;
		shift++;
	}
	return shift;
}

static int himfc_spi_probe(struct hifmc_host *host)
{
	int chipselect;
	unsigned int total = 0;
	unsigned char ids[NUMREAD_NUM + 1] = {0};
	struct spi_info *spiinfo = NULL;
	struct hifmc_spi *spi = host->spi;
	unsigned int reg_value = 0;

	host->num_chip = 0;
	for (chipselect = 0; chipselect < (CONFIG_FMC_MAX_CHIP + 1);
		chipselect++, spi++) {
		hifmc_read_chipid_cs(host, chipselect, ids, NUMREAD_NUM);

		/* if the ID is invalid,
		* continue to read the next one until succeeds
		*/
		if (!(ids[0] | ids[1] | ids[2]) || ((ids[0] & ids[1] & ids[2]) == 0xFF))
			continue;

		pr_info("Spi(cs%d) ID: 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X\n",
			chipselect, ids[0], ids[1], ids[2], ids[3], ids[4], ids[5]);

		spiinfo = spi_serach_ids(ids, sizeof(ids));
		if (spiinfo) {
			host->start_cs = chipselect;
			spi->name = spiinfo->name;
			spi->chipselect = chipselect;
			spi->chipsize = spiinfo->chipsize;
			spi->erasesize = spiinfo->erasesize;
			spi->addrcycle = spiinfo->addrcycle;
			spi->BP_bitmask = spiinfo->BP_bitmask;

			spi_search_rw(spiinfo, spi->read, HIFMC_SUPPORT_READ,
				HIFMC_SUPPORT_MAX_DUMMY, SPIINFO_READ_FLAG);

			spi->read->iftype = 0;
			spi->write->iftype = 0;

			spi_search_rw(spiinfo, spi->write,
				HIFMC_SUPPORT_WRITE, HIFMC_SUPPORT_MAX_DUMMY,
				SPIINFO_WRITE_FLAG);

			spi_get_erase(spiinfo, spi->erase, &spi->erasesize);

			reg_value = reg_value &
			(hifmc_map_chipsize(spi->chipsize) <<
				(unsigned int)(chipselect * NUMREAD_NUM));
			spi->iobase = (char *)host->iobase + total;
			host->num_chip++;
			total += spi->chipsize;

			DBG_MSG("spi->read->iftype:0x%02X, cmd:0x%02X, clock:0x%02X\n",
					spi->read->iftype, spi->read->cmd, spi->read->clock);
			DBG_MSG("spi->write->iftype:0x%02X, cmd:0x%02X, clock:0x%02X\n",
					spi->write->iftype, spi->write->cmd, spi->write->clock);
			pr_info("Spi(cs%d): ", spi->chipselect);
			pr_info("Block:%sB ", int_to_size(spi->erasesize));
			pr_info("Chip:%sB ", int_to_size(spi->chipsize));
			pr_info("(Name:%s)\n", spi->name);
		} else {
			pr_info("Spi(cs%d): Unknown\n", chipselect);
		}
	}

	return host->num_chip;
}

static int hifmc_probe(struct hifmc_host *host)
{
	struct mtd_info *mtd = host->mtd;

	if (!himfc_spi_probe(host))
		return -1;

	mtd->_erase = hifmc_erase;
	mtd->_write = hifmc_write;
	mtd->_read = hifmc_read;
	mtd->_lock = hifmc_lock;
	mtd->_unlock = hifmc_unlock;
#ifdef CONFIG_RTOS_OPTIMIZE_ONT
	mtd->read_chip_id = hifmc_read_chipid;
#endif
	if (hi_kernel_get_chip_id() == HI_CHIP_ID_5115S_E) {
		(void)hifmc_lock(mtd, 0, 0);
	} else {
		/* 116S chip, do not apply BP program to achieve flash protection
		*Direct use of chip power-off protection program
		*/
		(void)hifmc_unlock(mtd, 0, 0);
		fmc_protect_status = USER_PROTECT_OFF;
	}
	/* init delayed work */
	INIT_DELAYED_WORK(&spi_lock_work, hifmc_lock_timer_handler);
	return 0;
}

#define MAX_MTD_PARTITIONS 32

struct partition_entry {
	char name[ARRARY_LENGTH];
	unsigned long long start;
	unsigned long long length;
	unsigned int flags;
};

struct partition_info {
	int parts_num;
	struct partition_entry entry[MAX_MTD_PARTITIONS];
	struct mtd_partition parts[MAX_MTD_PARTITIONS];
};
static struct partition_info ptn_info;

static int __init parse_spi_partitions(const struct tag *tag)
{
	int i;

	if (tag->hdr.size <= HIMFC300_PARTITION_HEAD_LEN) {
		pr_info("%s(%d): tag->hdr.size <= 2\n", __func__, __LINE__);
		return 0;
	}
	ptn_info.parts_num = (tag->hdr.size - HIMFC300_PARTITION_HEAD_LEN) /
			(sizeof(struct partition_entry) / sizeof(int));
	if (ptn_info.parts_num > MAX_MTD_PARTITIONS) {
		pr_info("parts_num > %u\n", MAX_MTD_PARTITIONS);
		return 0;
	}
	memcpy(ptn_info.entry, &tag->u,
		ptn_info.parts_num * sizeof(struct partition_entry));

	for (i = 0; i < ptn_info.parts_num; i++) {
		ptn_info.parts[i].name = ptn_info.entry[i].name;
		ptn_info.parts[i].size = (ptn_info.entry[i].length);
		ptn_info.parts[i].offset = (ptn_info.entry[i].start);
		ptn_info.parts[i].mask_flags = 0;
	}

	return 0;
}
__tagtable(HIMFC300_TAG_TABLE_MAGIC, parse_spi_partitions);

static void hifmc_probe_spi_size(struct hifmc_host *host, struct mtd_info *mtd)
{
	int ix;
	struct hifmc_spi *spi = host->spi;
	int total = 0;
	int erasesize = host->spi[host->start_cs].erasesize;

	for (ix = 0; ix < (CONFIG_FMC_MAX_CHIP + 1); ix++, spi++)
		total += spi->chipsize;

	mtd->size = total;
	mtd->erasesize = erasesize;
}

#ifdef CONFIG_PM
static int hifmc_driver_suspend(struct platform_device *pltdev,
	pm_message_t state)
{
	struct hifmc_host *host = platform_get_drvdata(pltdev);

	if (host->suspend)
		return (host->suspend)(pltdev, state);

	return 0;
}

static int hifmc_driver_resume(struct platform_device *pltdev)
{
	struct hifmc_host *host = platform_get_drvdata(pltdev);

	if (host->resume)
		return (host->resume)(pltdev);

	return 0;
}
#endif /* CONFIG_PM */
static int spiflash_chipid_open(struct inode *inode, struct file *filp)
{
	return 0;
}

static int spiflash_chipid_release(struct inode *inode, struct file *file)
{
	return 0;
}

static ssize_t spiflash_chipid_read_interval(struct file *file,
	char __user *buffer, size_t buflen, loff_t *fpos)
{
	int count = 0;

	if (*fpos == HIMFC300_READ_CHIPID_MAGIC)
		return 0;

	count = snprintf(buffer, buflen, "%02x%02x%02x%02x%02x%02x%02x%02x\n",
		g_spi_ids[0], g_spi_ids[1], g_spi_ids[2], g_spi_ids[3],
		g_spi_ids[4], g_spi_ids[5], g_spi_ids[6], g_spi_ids[7]);

	*fpos = HIMFC300_READ_CHIPID_MAGIC;
	return count;
}

static ssize_t spiflash_chipid_write_interval(struct file *file,
	const char __user *buffer, size_t buflen, loff_t *fpos)
{
	return (ssize_t)buflen;
}

static const struct proc_ops proc_spiflashchipid = {
		.proc_read = spiflash_chipid_read_interval,
		.proc_open = spiflash_chipid_open,
		.proc_write = spiflash_chipid_write_interval,
		.proc_release = spiflash_chipid_release,
};

static int hifmc_driver_probe(struct platform_device *pltdev)
{
	int result = -EIO;
	struct hifmc_host *host = NULL;
	struct mtd_info *mtd = NULL;

	host = kzalloc(sizeof(struct hifmc_host), GFP_KERNEL);
	if (host == NULL)
		return -ENOMEM;

	memset(host, 0, sizeof(struct hifmc_host));

	platform_set_drvdata(pltdev, host);
	host->dev = &pltdev->dev;
	host->buffer = dma_alloc_coherent(host->dev, FMC_DMA_BUFFER_LEN,
		(dma_addr_t *)&host->dma_buffer, GFP_KERNEL);
	if (host->buffer == NULL) {
		kfree(host);
		return -ENOMEM;
	}

	host->regbase = ioremap_nocache(pltdev->resource[0].start,
		pltdev->resource[0].end - pltdev->resource[0].start + 1);
	if (!host->regbase) {
		pr_err("spi reg ioremap failed.\n");
		goto fail;
	}

	host->iobase = ioremap_nocache(pltdev->resource[1].start,
		pltdev->resource[1].end - pltdev->resource[1].start + 1);
	if (!host->iobase) {
		pr_err("spi buffer ioremap failed.\n");
		goto fail;
	}

	host->cfgreg = ioremap_nocache(pltdev->resource[2].start,
		pltdev->resource[2].end - pltdev->resource[2].start + 1);
	if (!host->cfgreg) {
		pr_err("spi cfgreg ioremap failed.\n");
		goto fail;
	}

	mutex_init(&host->lock);

	host->dma_buffer_size = FMC_DMA_BUFFER_LEN;
	memset(host->buffer, 0xFF, FMC_DMA_BUFFER_LEN);

	mtd = host->mtd;
	mtd->name = (char *)pltdev->name;
	mtd->type = MTD_NORFLASH;
	mtd->writesize = 1;
	mtd->flags = MTD_CAP_NORFLASH;
	mtd->owner = THIS_MODULE;

	if (hifmc_probe(host)) {
		result = -ENODEV;
		goto fail;
	}

	hifmc_probe_spi_size(host, mtd);
#ifdef CONFIG_RTOS_OPTIMIZE_ONT
	mtd->read_chip_id(mtd, g_spi_ids, 8);  // macro MTD_CHIP_ID_LEN = 8
#endif
	proc_create("flashchipid", 0, 0, &proc_spiflashchipid);

	if (mtd_has_partitions()) {
		int nr_partition = 0;

		pr_info("Hisilicon flash: ");
		pr_info("registering whole flash at once as master MTD\n");
		result = local_add_mtd_device(host->mtd);
		if (result) {
			pr_err("Hisilicon flash: register whole flash failedn\n");
			result = -ENOMEM;
			goto fail;
		}
#ifdef CONFIG_MTD_CMDLINE_PARTS
		INIT_LIST_HEAD(&host->mtd->partitions);
		nr_partition = local_parse_mtd_partitions(host->mtd, part_probes, 0);
#endif
		if (nr_partition > 0)
			return 0;

		result = -ENODEV;
		(void)mtd_device_unregister(mtd);
		goto fail;
	}

	if (!local_add_mtd_device(host->mtd))
		return 0;

	result = -ENODEV;

fail:
	if (host->regbase)
		iounmap(host->regbase);
	if (host->iobase)
		iounmap(host->iobase);
	if (host->cfgreg)
		iounmap(host->cfgreg);

	kfree(host);
	platform_set_drvdata(pltdev, NULL);

	return result;
}

static int hifmc_driver_remove(struct platform_device *pltdev)
{
	struct hifmc_host *host = platform_get_drvdata(pltdev);

	if (host->add_partition == 1) {
#ifdef CONFIG_MTD_PARTITIONS
		local_del_mtd_partitions(host->mtd);
#endif
	} else {
		local_del_mtd_device(host->mtd);
	}

	if (host->regbase)
		iounmap(host->regbase);
	if (host->iobase)
		iounmap(host->iobase);
	if (host->cfgreg)
		iounmap(host->cfgreg);

	kfree(host);
	platform_set_drvdata(pltdev, NULL);

	return 0;
}

static struct platform_driver hifmc_driver_pltdrv = {
	.probe = hifmc_driver_probe,
	.remove = hifmc_driver_remove,
#ifdef CONFIG_PM
	.suspend = hifmc_driver_suspend,
	.resume = hifmc_driver_resume,
#endif /* CONFIG_PM */
	.driver.name = "hi_fmc",
	.driver.owner = THIS_MODULE,
	.driver.bus = &platform_bus_type,
};

static struct resource hifmc_device_resources[] = {
	{
		.start = FMC_REG_BASE,
		.end = FMC_REG_BASE + FMC_REG_BASE_ADDRESS_LEN,
		.flags = IORESOURCE_MEM,
	},
	{
		.start = FMC_MEM_BASE,
		.end = FMC_MEM_BASE + FMC_BUFFER_BASE_ADDRESS_LEN,
		.flags = IORESOURCE_MEM,
	},
	{
		.start = CONFIG_HINFC301_PERIPHERY_REGBASE,
		.end = CONFIG_HINFC301_PERIPHERY_REGBASE + 100,
		.flags = IORESOURCE_MEM,
	},
};

static struct platform_device hifmc_device_pltdev = {
	.name = "hi_fmc",
	.id = -1,
	.dev.release = NULL,
	.num_resources = ARRAY_SIZE(hifmc_device_resources),
	.resource = hifmc_device_resources,
};

int hifmc_spinor_module_init(void)
{
	int result = 0;

	/* 0 ont 0xff mxu */
	if (hi_sfc_type_get() != SFC_TYPE_FLAG)
		return result;

	/* try to get base addr from DTS */
	hifmc_of_node_parse(&hifmc_device_pltdev, "hw,hifmc");

	result = platform_driver_register(&hifmc_driver_pltdrv);
	if (result < 0)
		return result;

	result = platform_device_register(&hifmc_device_pltdev);
	if (result < 0) {
		platform_driver_unregister(&hifmc_driver_pltdrv);
		return result;
	}

	pr_info("Hisilicon Flash Memory Controller Device Driver, Version 1.0\n");
	return result;
}

void hifmc_spinor_module_exit(void)
{
	platform_device_unregister(&hifmc_device_pltdev);
	platform_driver_unregister(&hifmc_driver_pltdrv);
}

MODULE_IMPORT_NS(HW_RTOS_NS);
